/*
*Copyright 2021-2023 NERCIS
*
*Licensed under the Apache License, Version 2.0 (the "License");
*you may not use this file except in compliance with the License.
*You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*Unless required by applicable law or agreed to in writing, software
*distributed under the License is distributed on an "AS IS" BASIS,
*WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*See the License for the specific language governing permissions and
*limitations under the License.
*/

package cn.ac.nercis.pes.service.evaluation;

import cn.ac.nercis.pes.common.utils.DataUtils;
import cn.ac.nercis.pes.common.constant.evaluation.EvaluationConclusion;
import cn.ac.nercis.pes.common.constant.knowledge.TargetType;
import cn.ac.nercis.pes.common.constant.project.FinishState;
import cn.ac.nercis.pes.model.dto.common.Result;
import cn.ac.nercis.pes.model.dto.common.ResultCode;
import cn.ac.nercis.pes.model.dto.evaluation.analyze.LayerEvaluationDTO;
import cn.ac.nercis.pes.model.dto.evaluation.analyze.OverallEvaluationDTO;
import cn.ac.nercis.pes.model.dto.evaluation.analyze.UnitEvaluationDTO;
import cn.ac.nercis.pes.model.dto.evaluation.onsite.SimpleEvalRecordDTO;
import cn.ac.nercis.pes.model.dto.evaluation.onsite.UnitTargetEvalDTO;
import cn.ac.nercis.pes.repository.dal.evaluation.EvaluationRecordRepository;
import cn.ac.nercis.pes.repository.dal.evaluation.LayerEvaluationRepository;
import cn.ac.nercis.pes.repository.dal.evaluation.OverallEvaluationRepository;
import cn.ac.nercis.pes.repository.dal.evaluation.UnitEvaluationRepository;
import cn.ac.nercis.pes.repository.dal.project.SystemTargetRepository;
import cn.ac.nercis.pes.repository.mapper.evaluation.UnitEvaluationMapper;
import cn.ac.nercis.pes.repository.model.evaluation.EvaluationRecordDO;
import cn.ac.nercis.pes.repository.model.evaluation.OverallEvaluationDO;
import cn.ac.nercis.pes.repository.model.evaluation.UnitEvaluationDO;
import cn.ac.nercis.pes.repository.model.project.SystemTargetDO;
import cn.ac.nercis.pes.repository.utils.DbUtils;
import cn.ac.nercis.pes.service.utils.AssetUtils;
import cn.ac.nercis.pes.service.utils.CalculateUtils;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 单元测评服务
 *
 * @author zpy
 * @version 1.0.0
 */
@Slf4j
@Service
public record UnitEvaluationService(SystemTargetRepository systemTargetRepository,
                                    UnitEvaluationRepository unitEvaluationRepository,
                                    EvaluationRecordRepository evaluationRecordRepository,
                                    OverallEvaluationRepository overallEvaluationRepository,
                                    LayerEvaluationRepository layerEvaluationRepository) {
    private final static String NEWLINE_CHAR = "\\n";
    private final static String ASSET_CONCLUSION_SPLIT = "：";

    @Autowired
    public UnitEvaluationService {
    }

    /**
     * 修复重复数据
     */
    public void repairDuplicateData(@NonNull String systemId, @NonNull String layerTargetId) {
        var source = this.unitEvaluationRepository.findAllBySystemIdAndLayerTargetId(systemId, layerTargetId);
        var unitTargetIds = source.stream().map(UnitEvaluationDO::getUnitTargetId).toList();
        for (var unitTargetId : unitTargetIds) {
            var unitTargetData = source.stream()
                    .filter(ue -> StringUtils.equals(ue.getUnitTargetId(), unitTargetId))
                    .toList();
            var duplicateData = DbUtils.findDuplicateData(unitTargetData);
            if (CollectionUtils.isNotEmpty(duplicateData)) {
                this.unitEvaluationRepository.deleteAll(duplicateData);
            }
        }
    }

    public List<UnitTargetEvalDTO> findUnitTargetByLayerTarget(@NonNull String systemId, @NonNull String layerTargetId) {
        this.repairDuplicateData(systemId, layerTargetId);
        List<UnitEvaluationDO> unitEvaluations = unitEvaluationRepository.findAllBySystemIdAndLayerTargetId(systemId, layerTargetId);
        return systemTargetRepository.findAllBySystemId(systemId)
                .stream()
                .filter(st -> StringUtils.equals(st.getKmParentId(), layerTargetId))
                .sorted(Comparator.comparingInt(SystemTargetDO::getKmSort))
                .map(t -> UnitTargetEvalDTO.builder()
                        .systemId(systemId)
                        .unitTargetId(t.getKmTargetId())
                        .unitTargetName(t.getKmName())
                        .finishState(getUnitTargetFinishState(t, unitEvaluations))
                        .build())
                .collect(Collectors.toList());
    }

    public Result<List<UnitEvaluationDTO>> findUnitEvaluationByLayerTarget(@NonNull String systemId, @NonNull String layerTargetId){
        var unitTargets = findUnitTargetByLayerTarget(systemId,layerTargetId);
        var unitEvalData =  unitTargets.stream()
                .map(ut->getUnitEvaluation(systemId,ut.getUnitTargetId()).getData())
                .filter(Objects::nonNull)
                .toList();
        if(CollectionUtils.isEmpty(unitEvalData)){
            return Result.failed(ResultCode.EMPTY_EVALUATION_RECORD_BY_UNIT_TARGET);
        }
        return Result.success(unitEvalData);
    }

    public Result<UnitEvaluationDTO> getUnitEvaluation(@NonNull String systemId, @NonNull String unitTargetId) {
        List<EvaluationRecordDO> evaluationRecords = evaluationRecordRepository.findAllBySystemIdAndUnitTargetId(systemId, unitTargetId);
        SystemTargetDO systemTargetDO = systemTargetRepository.findBySystemIdAndKmTargetId(systemId, unitTargetId)
                .orElse(null);
        if (Objects.isNull(systemTargetDO)) {
            return Result.failed(ResultCode.SYSTEM_TARGET_NOT_EXIST);
        }
        if (CollectionUtils.isEmpty(evaluationRecords) && systemTargetDO.getIsApply()) {
            return Result.failed(ResultCode.EMPTY_EVALUATION_RECORD_BY_UNIT_TARGET);
        }
        return unitEvaluationRepository.getBySystemIdAndUnitTargetId(systemId, unitTargetId)
                .map(UnitEvaluationMapper.INSTANCE::toDTO)
                .map(unitEvaluationDTO -> {
                    unitEvaluationDTO.setIsApply(systemTargetDO.getIsApply());
                    unitEvaluationDTO.setHasRecord(CollectionUtils.isNotEmpty(evaluationRecords));
                    unitEvaluationDTO.setUnitTargetName(systemTargetDO.getKmName());
                    if (systemTargetDO.getIsApply()) {
                        unitEvaluationDTO.setConclusion(CalculateUtils.getUnitEvalConclusion(evaluationRecords));
                    } else {
                        unitEvaluationDTO.setConclusion(EvaluationConclusion.NOT_APPLICABLE);
                    }
                    return Result.success(unitEvaluationDTO);
                })
                .orElse(Result.success(defaultUnitEvaluation(evaluationRecords, systemTargetDO)));
    }

    public List<SimpleEvalRecordDTO> findEvaluationRecords(@NonNull String systemId, @NonNull String unitTargetId) {
        var unitTarget = this.systemTargetRepository.findBySystemIdAndKmTargetId(systemId,unitTargetId).orElse(null);
        return evaluationRecordRepository.findAllBySystemIdAndUnitTargetId(systemId, unitTargetId)
                .stream()
                .map(er -> SimpleEvalRecordDTO.builder()
                        .id(er.getId())
                        .systemId(er.getSystemId())
                        .unitTargetId(er.getUnitTargetId())
                        .unitTargetName(Objects.nonNull(unitTarget)?unitTarget.getKmName():StringUtils.EMPTY)
                        .assetName(AssetUtils.getAssetName(er.getAsset()))
                        .conclusion(er.getConclusion().getName())
                        .content(getEvalRecordContent(er,unitTarget))
                        .build())
                .collect(Collectors.toList());
    }

    public Result<UnitEvaluationDTO> saveUnitEvaluation(@NonNull UnitEvaluationDTO recordDTO) {
        if (StringUtils.isBlank(recordDTO.getId())) {
            return createUnitEvaluation(recordDTO);
        } else {
            return modifyUnitEvaluation(recordDTO);
        }
    }

    public Result<String> batchSaveUnitEvaluation(List<UnitEvaluationDTO> data){
        Result<String> result = Result.success();
        for(var item : data){
            var itemResult = saveUnitEvaluation(item);
            if(!itemResult.isSuccess()){
                result = Result.failed(ResultCode.FAIL);
                break;
            }
        }
        return result;
    }

    public Result<String> setUnitEvalFinishState(@NonNull String systemId,@NonNull String layerTargetId, @NonNull FinishState state) {
        var unitEvaluations = unitEvaluationRepository.findAllBySystemIdAndLayerTargetId(systemId,layerTargetId)
                .stream()
                .peek(ue->{
                    ue.setFinishState(state);
                    ue.setModifyTime(LocalDateTime.now());
                })
                .toList();
        if (CollectionUtils.isEmpty(unitEvaluations)) {
            return Result.failed(ResultCode.UNIT_EVALUATION_RESULT_ANALYSIS_NOT_EXIST);
        }
        unitEvaluationRepository.saveAll(unitEvaluations);
        return Result.success();
    }

    public Result<String> setUnitEvalFinishState(@NonNull String unitEvalId, @NonNull FinishState state) {
        UnitEvaluationDO entity = unitEvaluationRepository.findById(unitEvalId).orElse(null);
        if (Objects.isNull(entity)) {
            return Result.failed(ResultCode.UNIT_EVALUATION_RESULT_ANALYSIS_NOT_EXIST);
        }
        entity.setFinishState(state);
        entity.setModifyTime(LocalDateTime.now());
        return Optional.of(entity)
                .map(unitEvaluationRepository::save)
                .map(ue -> {
                    refreshOverallEvalFinishState(ue.getSystemId(), ue.getLayerTargetId());
                    return Result.success(ue.getId());
                })
                .orElse(Result.failed());
    }

    /**
     * 修复总体评价重复数据
     */
    public void repairOverallDuplicateData(@NonNull String systemId, @NonNull String layerTargetId) {
        var source = this.overallEvaluationRepository.findBySystemIdAndLayerTargetId(systemId, layerTargetId);
        var duplicateData = DbUtils.findDuplicateData(source);
        if (CollectionUtils.isNotEmpty(duplicateData)) {
            this.overallEvaluationRepository.deleteAll(duplicateData);
        }
    }

    public OverallEvaluationDTO getOverallEvaluation(@NonNull String systemId, @NonNull String layerTargetId) {
        this.repairOverallDuplicateData(systemId, layerTargetId);
        return DataUtils.findFirst(overallEvaluationRepository.findBySystemIdAndLayerTargetId(systemId, layerTargetId))
                .map(UnitEvaluationMapper.INSTANCE::toDTO)
                .orElse(OverallEvaluationDTO.builder()
                        .systemId(systemId)
                        .layerTargetId(layerTargetId)
                        .content("")
                        .build());
    }

    public Result<OverallEvaluationDTO> saveOverallEvaluation(@NonNull OverallEvaluationDTO evaluationDTO) {
        if (StringUtils.isBlank(evaluationDTO.getId())) {
            return createOverallEvaluation(evaluationDTO);
        } else {
            return modifyOverallEvaluation(evaluationDTO);
        }
    }

    public void refreshOverallEvalFinishState(@NonNull String systemId, @NonNull String layerTargetId) {
        OverallEvaluationDO entity = DataUtils.getFirst(overallEvaluationRepository.findBySystemIdAndLayerTargetId(systemId, layerTargetId));
        if (Objects.isNull(entity)) {
            return;
        }
        List<UnitEvaluationDO> unitEvaluations = unitEvaluationRepository.findAllBySystemIdAndLayerTargetId(systemId, layerTargetId);
        long finishCount = unitEvaluations.stream().filter(ue -> ue.getFinishState() == FinishState.FINISH).count();
        long count = systemTargetRepository.findAllBySystemId(systemId)
                .stream()
                .filter(st -> StringUtils.equals(st.getKmParentId(), layerTargetId))
                .count();
        entity.setFinishState(finishCount == count ? FinishState.FINISH : FinishState.INCOMPLETE);
        overallEvaluationRepository.save(entity);
        this.setLayerEvalFinishState(systemId, layerTargetId, entity.getFinishState());
    }

    public LayerEvaluationDTO getLayerEvaluation(@NonNull String systemId, @NonNull String layerTargetId) {
        return DataUtils.findFirst(layerEvaluationRepository.findBySystemIdAndLayerTargetId(systemId, layerTargetId))
                .map(UnitEvaluationMapper.INSTANCE::toLayerEvaluationDTO)
                .orElse(LayerEvaluationDTO.builder()
                        .systemId(systemId)
                        .layerTargetId(layerTargetId)
                        .resultAnalysis("")
                        .build());
    }

    public Result<LayerEvaluationDTO> saveLayerEvaluation(@NonNull LayerEvaluationDTO evaluationDTO) {
        if (StringUtils.isBlank(evaluationDTO.getId())) {
            return createLayerEvaluation(evaluationDTO);
        } else {
            return modifyLayerEvaluation(evaluationDTO);
        }
    }

    private void setLayerEvalFinishState(@NonNull String systemId, @NonNull String layerTargetId, FinishState state) {
        DataUtils.findFirst(layerEvaluationRepository.findBySystemIdAndLayerTargetId(systemId, layerTargetId))
                .ifPresent(entity -> {
                    entity.setFinishState(state);
                    layerEvaluationRepository.save(entity);
                });
    }

    private Result<UnitEvaluationDTO> createUnitEvaluation(UnitEvaluationDTO evaluationDTO) {
        return Optional.of(UnitEvaluationMapper.INSTANCE.toDO(evaluationDTO))
                .map(er -> {
                    er.setFinishState(FinishState.INCOMPLETE);
                    er.setCreateTime(LocalDateTime.now());
                    return er;
                })
                .map(unitEvaluationRepository::save)
                .map(UnitEvaluationMapper.INSTANCE::toDTO)
                .map(unitEvaluationDTO -> {
                    unitEvaluationDTO.setConclusion(evaluationDTO.getConclusion());
                    unitEvaluationDTO.setIsApply(evaluationDTO.getIsApply());
                    unitEvaluationDTO.setHasRecord(evaluationDTO.getHasRecord());
                    return unitEvaluationDTO;
                })
                .map(Result::success)
                .orElse(Result.failed());
    }

    private Result<UnitEvaluationDTO> modifyUnitEvaluation(UnitEvaluationDTO evaluationDTO) {
        UnitEvaluationDO entity = unitEvaluationRepository.findById(evaluationDTO.getId()).orElse(null);
        if (Objects.isNull(entity)) {
            return Result.failed(ResultCode.UNIT_EVALUATION_RESULT_ANALYSIS_NOT_EXIST);
        }
        entity.setResultAnalysis(evaluationDTO.getResultAnalysis());
        entity.setModifyTime(LocalDateTime.now());
        return Optional.of(entity)
                .map(unitEvaluationRepository::save)
                .map(UnitEvaluationMapper.INSTANCE::toDTO)
                .map(unitEvaluationDTO -> {
                    unitEvaluationDTO.setConclusion(evaluationDTO.getConclusion());
                    unitEvaluationDTO.setIsApply(evaluationDTO.getIsApply());
                    unitEvaluationDTO.setHasRecord(evaluationDTO.getHasRecord());
                    return unitEvaluationDTO;
                })
                .map(Result::success)
                .orElse(Result.failed());
    }

    private Result<OverallEvaluationDTO> createOverallEvaluation(OverallEvaluationDTO evaluationDTO) {
        return Optional.of(UnitEvaluationMapper.INSTANCE.toDO(evaluationDTO))
                .map(er -> {
                    er.setFinishState(FinishState.INCOMPLETE);
                    er.setCreateTime(LocalDateTime.now());
                    return er;
                })
                .map(overallEvaluationRepository::save)
                .map(UnitEvaluationMapper.INSTANCE::toDTO)
                .map(Result::success)
                .orElse(Result.failed());
    }

    private Result<OverallEvaluationDTO> modifyOverallEvaluation(OverallEvaluationDTO evaluationDTO) {
        OverallEvaluationDO entity = overallEvaluationRepository.findById(evaluationDTO.getId()).orElse(null);
        if (Objects.isNull(entity)) {
            return Result.failed(ResultCode.NOT_FOUND);
        }
        entity.setContent(evaluationDTO.getContent());
        entity.setModifyTime(LocalDateTime.now());
        return Optional.of(entity)
                .map(overallEvaluationRepository::save)
                .map(UnitEvaluationMapper.INSTANCE::toDTO)
                .map(Result::success)
                .orElse(Result.failed());
    }

    private Result<LayerEvaluationDTO> createLayerEvaluation(LayerEvaluationDTO evaluationDTO) {
        return Optional.of(UnitEvaluationMapper.INSTANCE.toLayerEvaluationDO(evaluationDTO))
                .map(er -> {
                    er.setFinishState(FinishState.INCOMPLETE);
                    er.setCreateTime(LocalDateTime.now());
                    return er;
                })
                .map(layerEvaluationRepository::save)
                .map(UnitEvaluationMapper.INSTANCE::toLayerEvaluationDTO)
                .map(Result::success)
                .orElse(Result.failed());
    }

    private Result<LayerEvaluationDTO> modifyLayerEvaluation(LayerEvaluationDTO evaluationDTO) {
        var entity = layerEvaluationRepository.findById(evaluationDTO.getId()).orElse(null);
        if (Objects.isNull(entity)) {
            return Result.failed(ResultCode.NOT_FOUND);
        }
        entity.setResultAnalysis(evaluationDTO.getResultAnalysis());
        entity.setModifyTime(LocalDateTime.now());
        return Optional.of(entity)
                .map(layerEvaluationRepository::save)
                .map(UnitEvaluationMapper.INSTANCE::toLayerEvaluationDTO)
                .map(Result::success)
                .orElse(Result.failed());
    }

    private String getEvalRecordContent(EvaluationRecordDO recordDO,SystemTargetDO unitTarget) {
        if (Objects.nonNull(unitTarget) && unitTarget.getKmType() == TargetType.TECHNOLOGY
                && recordDO.getConclusion() != EvaluationConclusion.NOT_APPLICABLE) {
            return Stream.of(recordDO.getDContent(), recordDO.getAContent(), recordDO.getKContent())
                    .filter(StringUtils::isNotEmpty)
                    .collect(Collectors.joining(NEWLINE_CHAR));
        } else {
            return recordDO.getContent();
        }
    }

    private UnitEvaluationDTO defaultUnitEvaluation(List<EvaluationRecordDO> evaluationRecords, SystemTargetDO systemTargetDO) {
        EvaluationRecordDO defaultRecord = evaluationRecords.stream().findFirst().orElse(null);
        if (Objects.isNull(defaultRecord) && !systemTargetDO.getIsApply()) {
            return UnitEvaluationDTO.builder()
                    .systemId(systemTargetDO.getSystemId())
                    .layerTargetId(systemTargetDO.getKmParentId())
                    .unitTargetId(systemTargetDO.getKmTargetId())
                    .unitTargetName(systemTargetDO.getKmName())
                    .isApply(systemTargetDO.getIsApply())
                    .hasRecord(CollectionUtils.isNotEmpty(evaluationRecords))
                    .conclusion(EvaluationConclusion.NOT_APPLICABLE)
                    .resultAnalysis(systemTargetDO.getReasons())
                    .build();
        } else if (Objects.nonNull(defaultRecord)) {
            return UnitEvaluationDTO.builder()
                    .systemId(systemTargetDO.getSystemId())
                    .layerTargetId(defaultRecord.getLayerTargetId())
                    .unitTargetId(defaultRecord.getUnitTargetId())
                    .unitTargetName(systemTargetDO.getKmName())
                    .isApply(systemTargetDO.getIsApply())
                    .hasRecord(CollectionUtils.isNotEmpty(evaluationRecords))
                    .conclusion(CalculateUtils.getUnitEvalConclusion(evaluationRecords))
                    .build();
        } else {
            return null;
        }
    }

    private String defaultUnitResultAnalysis(List<EvaluationRecordDO> evaluationRecords) {
        return evaluationRecords.stream()
                .map(this::defaultAssetResultAnalysis)
                .collect(Collectors.joining(NEWLINE_CHAR));
    }

    private String defaultAssetResultAnalysis(EvaluationRecordDO recordDO) {
        StringBuilder content = new StringBuilder();
        content.append(AssetUtils.getAssetName(recordDO.getAsset())).append(ASSET_CONCLUSION_SPLIT).append(recordDO.getConclusion().getName());
        var unitTarget = this.systemTargetRepository.findBySystemIdAndKmTargetId(recordDO.getSystemId(),recordDO.getUnitTargetId())
                .orElse(null);
        if (Objects.nonNull(unitTarget) && unitTarget.getKmType() == TargetType.TECHNOLOGY
                && recordDO.getConclusion() != EvaluationConclusion.NOT_APPLICABLE) {
            if (StringUtils.isNotBlank(recordDO.getDContent())) {
                content.append(NEWLINE_CHAR).append(recordDO.getDContent());
            }
            if (StringUtils.isNotBlank(recordDO.getAContent())) {
                content.append(NEWLINE_CHAR).append(recordDO.getAContent());
            }
            if (StringUtils.isNotBlank(recordDO.getKContent())) {
                content.append(NEWLINE_CHAR).append(recordDO.getKContent());
            }
        } else {
            content.append(NEWLINE_CHAR).append(recordDO.getContent());
        }
        return content.toString();
    }

    public FinishState getUnitTargetFinishState(SystemTargetDO targetDO, List<UnitEvaluationDO> unitEvaluations) {
        return unitEvaluations.stream()
                .filter(er -> StringUtils.equals(er.getUnitTargetId(), targetDO.getKmTargetId()))
                .map(UnitEvaluationDO::getFinishState)
                .findFirst()
                .orElse(FinishState.NONE);
    }
}